using System;
using System.Collections.Generic;
using YouVisio.Silverlight.TextDiff.Extensions;

namespace YouVisio.Silverlight.TextDiff
{
    public class StringHashInfo
    {
        private readonly List<string> _unique = new List<string>();
        private readonly Dictionary<string, int> _hashes = new Dictionary<string, int>();
        private readonly List<int> _leftListOfHashes = new List<int>();
        private readonly List<int> _rightListOfHashes = new List<int>();

        private StringHashInfo() { }

        public static StringHashInfo CreateForLines(string left, string right, int startIndex = -1, int endIndex = -1)
        {
            var info = left.FoldEachLine(new StringHashInfo(),
                                  (x, line) =>
                                  {
                                      if (x._hashes.ContainsKey(line))
                                      {
                                          x._leftListOfHashes.Add(x._hashes[line]);
                                      }
                                      else
                                      {
                                          x._unique.Add(line);
                                          var lastIndex = x._unique.Count - 1;
                                          x._hashes.Add(line, lastIndex);
                                          x._leftListOfHashes.Add(lastIndex);
                                      }
                                      return x;
                                  }, startIndex, endIndex);
            return right.FoldEachLine(info,
                                  (x, line) =>
                                  {
                                      if (x._hashes.ContainsKey(line))
                                      {
                                          x._rightListOfHashes.Add(x._hashes[line]);
                                      }
                                      else
                                      {
                                          x._unique.Add(line);
                                          var lastIndex = x._unique.Count - 1;
                                          x._hashes.Add(line, lastIndex);
                                          x._rightListOfHashes.Add(lastIndex);
                                      }
                                      return x;
                                  }, startIndex, endIndex);
        }


        public IList<int> LeftListHashes { get { return _leftListOfHashes; } }
        public IList<int> RightListHashes { get { return _rightListOfHashes; } }
        public IList<string> Unique { get { return _unique; } }


        public TAccumulator FoldTextParts<TAccumulator>(TAccumulator accumulator, DiffPartsList diffParts, Func<TAccumulator, TextPart, TAccumulator> func)
        {
            if (diffParts == null) return default(TAccumulator);

            return diffParts.FoldGroups(new FoldTextPartsState<TAccumulator>(0, 0, this, accumulator, func), OnEachDifferenceGroup).Acc;
        }

        private static FoldTextPartsState<TAccumulator> OnEachDifferenceGroup<TAccumulator>(FoldTextPartsState<TAccumulator> acc, Operation opp, int num)
        {
            var ini = acc.InitialIndex;
            var chg = acc.ChangedIndex;
            string[] initial;
            string[] changed;
            if (opp == Operation.Equal)
            {
                acc.InitialIndex += num;
                acc.ChangedIndex += num;
                initial = changed = new string[num];
            }
            else if (opp == Operation.Insert)
            {
                acc.ChangedIndex += num;
                initial = null;
                changed = new string[num];
            }
            else if (opp == Operation.Delete)
            {
                acc.InitialIndex += num;
                initial = new string[num];
                changed = null;
            }
            else if (opp == Operation.Change)
            {
                acc.InitialIndex += num;
                acc.ChangedIndex += num;
                initial = new string[num];
                changed = new string[num];
            }
            else return acc; // if it's unknown skip it
            for (var i = 0; i < num; i++)
            {
                if (initial != null) initial[i] = acc.Info._unique[acc.Info._leftListOfHashes[ini + i]];
                if (changed != null &&
                    opp != Operation.Equal) changed[i] = acc.Info._unique[acc.Info._rightListOfHashes[chg + i]];
            }
            acc.Acc = acc.Func(acc.Acc, new TextPart(opp, initial, changed));
            return acc;
        }

        internal struct FoldTextPartsState<TAccumulator>
        {
            public FoldTextPartsState(int i, int j, StringHashInfo nfo, TAccumulator acc, Func<TAccumulator, TextPart, TAccumulator> func)
            {
                InitialIndex = i;
                ChangedIndex = j;
                Info = nfo;
                Acc = acc;
                Func = func;
            }

            public int InitialIndex;
            public int ChangedIndex;
            public StringHashInfo Info;
            public TAccumulator Acc;
            public Func<TAccumulator, TextPart, TAccumulator> Func;
        }
    }
}